#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>

#include "ThreadPool.h"
#include "LinkQueue.h"
#include "DoubleLinkList.h"
#include "StdThread.h"


#define true 1
#define false 0
#define Sleep_Time 10

struct ThreadTask
{
    void *(*function)(void*);
    void *arg;
};

typedef struct ThreadTask task;
task *CreateTask(void *(*function)(void*),void *arg)
{
    task *t=(task*)malloc(sizeof(task));
    if(t==NULL)
    {
        printf("CreateTask malloc error\n");
        return NULL;
    }
    t->function=function;
    t->arg=arg;

}
void FreeTask(task *t)
{
    free(t);
}
struct ThreadPool
{
    DLList threads;
    LQueue task_queue;

    int max_thread_num;
    int min_thread_num;
    int max_queue_size;
    int shutdown;
    int busy_thread_num;
    int waitKillthread;

    pthread_mutex_t pool_mutex;
    pthread_mutex_t busy_num_mutex;
    pthread_cond_t queue_not_empty;
    pthread_cond_t queue_not_full;

    thread *admin_thread;
};





void *thread_work(void *arg)
{
    ThreadP *p=(ThreadP *)arg;
    while(1)
    {
        pthread_mutex_lock(&p->pool_mutex);
        while(IsQEmpty(&p->task_queue)==true&&p->shutdown==false)
        {
            pthread_cond_wait(&p->queue_not_empty,&p->pool_mutex);
            if(p->waitKillthread>0)
                if(p->busy_thread_num*2<GetListLen(&p->threads)&&GetListLen(&p->threads)>p->min_thread_num)
                {
                    struct Node* TravelPoint=p->threads.head;
                    while(TravelPoint!=NULL)
                    {
                        thread *t=(thread *)TravelPoint->data;
                        if(GetThreadId(t)==pthread_self())
                        {
                            RemoveByElement(&p->threads,t);
                            break;
                        }
                        TravelPoint=TravelPoint->next;
                    }
                    pthread_mutex_unlock(&p->pool_mutex);
                    pthread_exit(NULL);
                }
        }
        if(p->shutdown==true)
        {
            pthread_mutex_unlock(&p->pool_mutex);
            pthread_exit(NULL);
        }
        task *mission=(task *)(*(QPop(&p->task_queue)));
        pthread_cond_broadcast(&p->queue_not_full);
        pthread_mutex_unlock(&p->pool_mutex);
        pthread_mutex_lock(&p->busy_num_mutex);
        p->busy_thread_num++;
        pthread_mutex_unlock(&p->busy_num_mutex);
        mission->function(mission->arg);
        pthread_mutex_lock(&p->busy_num_mutex);
        p->busy_thread_num--;
        pthread_mutex_unlock(&p->busy_num_mutex);
    }
}


void *thread_manager(void *arg)
{
    ThreadP *p=(ThreadP *)arg;
    while(p->shutdown==false)
    {
        sleep(Sleep_Time);
        pthread_mutex_lock(&p->pool_mutex);
        if(GetQueueLen(&p->task_queue)>p->busy_thread_num&&GetListLen(&p->threads)<p->max_thread_num)
        {
            int addnum=(GetQueueLen(&p->task_queue)-p->busy_thread_num)/2;
            for(int i=0;i<addnum;i++)
            {
                thread *t=InitThread(thread_work,p);
                InsertTail(&p->threads,t);
            }
        }
        
        int busythread=p->busy_thread_num;
        int threadlen=GetListLen(&p->threads);
        p->waitKillthread=(threadlen-busythread)/2<threadlen-p->min_thread_num?(threadlen-busythread)/2:threadlen-p->min_thread_num;
        int minusnum=p->waitKillthread;
        pthread_mutex_unlock(&p->pool_mutex);
        if(busythread*2<GetListLen(&p->threads)&&threadlen>p->min_thread_num)
        {
            for(int i=0;i<minusnum;i++)
                pthread_cond_broadcast(&p->queue_not_empty);
        }
    }
    pthread_exit(NULL);
}

ThreadP *InitThreadPool(int max_thread_num, int min_thread_num, int max_queue_size)
{
    ThreadP *p=(ThreadP *)malloc(sizeof(ThreadP));
    if(p==NULL)
    {
        printf("InitThreadPool malloc error\n");
        return NULL;
    }
    
    InitDLinkList(&p->threads);
    InitLQueue(&p->task_queue);
    p->shutdown=false;
    p->max_queue_size=max_queue_size;
    p->max_thread_num=max_thread_num;
    p->min_thread_num=min_thread_num;
    p->busy_thread_num=0;
    p->waitKillthread=0;
    pthread_mutex_init(&p->pool_mutex,NULL);
    pthread_mutex_init(&p->busy_num_mutex,NULL);
    pthread_cond_init(&p->queue_not_empty,NULL);
    pthread_cond_init(&p->queue_not_full,NULL);

    
    for(int i=0;i<max_thread_num;i++)
    {
        thread *t=InitThread(thread_work,p);
        InsertTail(&p->threads,t);
    }
    p->admin_thread=InitThread(thread_manager,p);
    return p;
}

void ThreadP_AddTask(ThreadP *p, void *(*func)(void *), void *arg)
{
    pthread_mutex_lock(&p->pool_mutex);
    while(GetQueueLen(&p->task_queue)==p->max_queue_size&&p->shutdown==false)
    {
        pthread_cond_wait(&p->queue_not_full,&p->pool_mutex);
    }
    if(p->shutdown==true)
    {
        pthread_mutex_unlock(&p->pool_mutex);
        return;
    }
    QPush(&p->task_queue,CreateTask(func,arg));
    pthread_cond_broadcast(&p->queue_not_empty);
    pthread_mutex_unlock(&p->pool_mutex);
}
void ThreadElementFree(ElementType element)
{
    // free((char*)element);
}
void DestoryThreadPool(ThreadP *p)
{
    if(p==NULL)
    {
        return;
    }
    p->shutdown=true;
    JoinThread(p->admin_thread);
    int len=GetListLen(&p->threads);
    for(int i=0;i<len;i++)
    {
        pthread_cond_broadcast(&p->queue_not_empty);
    }
    struct Node *TravelPoint=p->threads.head;
    while(TravelPoint!=NULL)
    {
        thread *t=(thread*)TravelPoint->data;
        JoinThread(t);
        free(t);
        TravelPoint=TravelPoint->next;
    }
    FreeDLinkList(&p->threads,ThreadElementFree);
    while(IsQEmpty(&p->task_queue)!=true)
    {
        void *task=*QPop(&p->task_queue);
        free(task);
    }
    FreeLQueue(&p->task_queue);
    pthread_mutex_destroy(&p->pool_mutex);
    pthread_mutex_destroy(&p->busy_num_mutex);
    pthread_cond_destroy(&p->queue_not_full);
    pthread_cond_destroy(&p->queue_not_empty);
    free(p);
}